#include "engine.h"
#include <QPainter>
#include <QPrinter>
#include <math.h>

Engine::Engine(PioCore *core, QObject *parent) :
    QThread(parent),
    m_core(core),
    m_quit(false),
    m_enabled(false),
    m_autorotate(false),
    m_item_width(3),
    m_item_height(4),
    m_size_mode(CropImage),
    m_markers(false)
{
    start();
}

Engine::~Engine()
{
    m_mutex.lock();
    m_quit = true;
    m_cond.wakeAll();
    m_mutex.unlock();
    wait();
}

void Engine::setSelection(QList<MediaItemPtr> selection)
{
    QMutexLocker ml(&m_mutex);
    m_selection.clear();
    foreach(MediaItemPtr item, selection)
        m_selection.append(item->metadata());
    // remove image data for items that are no longer selected
    bool items_removed = false;
    QList<QString> keys = m_images.keys();
    foreach(QString str, keys) {
        bool found = false;
        foreach(MediaItem::Metadata m, m_selection) {
            if(m.filename == str) {
                found = true;
                break;
            }
        }
        if(!found) {
            m_images.remove(str);
            items_removed = true;
        }
    }
    m_cond.wakeAll();
    if(items_removed)
        emit changed();
}

void Engine::print(QPrinter *printer)
{
    QList<QImage> images;
    m_mutex.lock();
    foreach(MediaItem::Metadata m, m_selection) {
        QImage & img = m_images[m.filename].image;
        if(!img.isNull())
            images.append(img);
    }
    m_mutex.unlock();

    printer->setFullPage(true);
    QSizeF psize = printer->paperSize(QPrinter::Millimeter);
    double width = psize.width() / 10;
    double height = psize.height() / 10;

    double m = 0.5, mb = 1, mt = 1, ml = 1, mr = 1;

    QPainter painter;
    painter.begin(printer);
    painter.scale(printer->resolution() / 2.54, printer->resolution() / 2.54);

    double y = mt;
    while(true) {
        QList<QImage> row = takeRow(images, width - ml - mr, m);
        if(row.empty())
            break;

        double w = 0;
        double h = 0;

        foreach(QImage img, row) {
            double iw = toCentimeters(img.width());
            double ih = toCentimeters(img.height());
            w += iw;
            h = std::max(h, ih);
        }

        if(y > mt && y + m + h > height - mb) {
            printer->newPage();
            y = mt;
        }

        double space = 0;
        if(row.size() > 1)
            space = (width - w - ml - mr) / (double)(row.size() - 1.0);
        qDebug("page width: %.1f images width: %.1f #images: %d space: %.1f ml: %.1f mr: %.1f", width, w, row.size(), space, ml, mr);
        double x = ml;
        foreach(QImage img, row) {
            double iw = toCentimeters(img.width());
            double ih = toCentimeters(img.height());

            QRectF target;
            if(row.size() == 1)
                target = QRectF((width - iw) / 2.0, y, iw, ih);
            else
                target = QRectF(x, y + (h - ih) / 2.0, iw, ih);
            QRectF source(0, 0, img.width(), img.height());
            painter.drawImage(target, img, source);
            if(m_markers)
                printMarkers(painter, target);
            x += iw + space;
        }
        y += h + m;
    }

    painter.end();
}

void Engine::run()
{
    m_mutex.lock();
    while(!m_quit) {
        if(m_enabled) {
            MediaItem::Metadata m;
            if(getFirstInvalidItem(m)) {
                emit completed(false);
                QSize isize(toPixels(m_item_width), toPixels(m_item_height));
                ImageInfo ii = m_images[m.filename];
                m_mutex.unlock();

                QImage image = m_core->load(m.filename, QSize());
                int o = 0;
                QSize originalSize;
                if(o & 1)
                    originalSize = QSize(image.height(), image.width());
                else
                    originalSize = image.size();

                m_mutex.lock();
                SizeMode mode;
                bool rotate;
                getOperations(originalSize, mode, rotate);
                m_mutex.unlock();

                if(rotate)
                    o = (o + 1) % 4;

                QSize size = isize;
                if(o & 1)
                    size = QSize(size.height(), size.width());

                if(mode == CropImage) {
                    image = image.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
                    image = image.copy((image.width() - size.width()) / 2, (image.height() - size.height()) / 2, size.width(), size.height());
                }
                else if(mode == ShrinkImage) {
                    image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
                    QRect inset((size.width() - image.width()) / 2, (size.height() - image.height()) / 2, image.width(), image.height());
                    image = image.copy((image.width() - size.width()) / 2, (image.height() - size.height()) / 2, size.width(), size.height());
                    for(int x = 0; x < image.width(); x++) for(int y = 0; y < image.height(); y++)
                        if(!inset.contains(x, y))
                            image.setPixel(x, y, qRgb(255, 255, 255));
                }
                else if((mode == MatchWidth && (o & 1) == 0) || (mode == MatchHeight && (o & 1) != 0)) {
                    int w = size.width();
                    int h = size.width() * image.height() / (double)image.width() + 0.5;
                    image = image.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
                }
                else {
                    int w = size.height() * image.width() / (double)image.height() + 0.5;
                    int h = size.height();
                    image = image.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
                }

                if(o) {
                    QTransform transform;
                    transform.rotate(90 * o);
                    image = image.transformed(transform);
                }
                ii.image        = image;
                ii.originalSize = originalSize;
                ii.mode         = mode;
                ii.rotate       = rotate;

                m_mutex.lock();
                bool found = false;
                foreach(MediaItem::Metadata it, m_selection) {
                    if(it.filename == m.filename) {
                        found = true;
                        break;
                    }
                }
                if(found) {
                    m_images[m.filename] = ii;
                    emit changed();
                }
                continue;
            }
            else
                emit completed(true);
        }
        m_cond.wait(&m_mutex);
    }
    m_mutex.unlock();
}

bool Engine::getFirstInvalidItem(MediaItem::Metadata & m)
{
    QSize isize(toPixels(m_item_width), toPixels(m_item_height));
    foreach(MediaItem::Metadata it, m_selection) {
        ImageInfo & ii = m_images[it.filename];
        if(ii.image.isNull()) {
            m = it;
            return true;
        }
        SizeMode mode;
        bool rotate;
        getOperations(ii.originalSize, mode, rotate);
        if(ii.mode != mode) {
            m = it;
            return true;
        }
        if(rotate != ii.rotate) {
            m = it;
            return true;
        }
        QSize size = isize;
        if(mode == MatchWidth) {
            if(rotate) {
                int h = isize.width() * ii.originalSize.width() / (double)ii.originalSize.height() + 0.5;
                size.setHeight(h);
            }
            else {
                int h = isize.width() * ii.originalSize.height() / (double)ii.originalSize.width() + 0.5;
                size.setHeight(h);
            }
        }
        else if(mode == MatchHeight) {
            if(rotate) {
                int w = isize.height() * ii.originalSize.height() / (double)ii.originalSize.width() + 0.5;
                size.setWidth(w);
            }
            else {
                int w = isize.height() * ii.originalSize.width() / (double)ii.originalSize.height() + 0.5;
                size.setWidth(w);
            }
        }
        if(size != ii.image.size()) {
            m = it;
            return true;
        }
    }
    return false;
}

void Engine::getOperations(const QSize & originalSize, Engine::SizeMode &mode, bool &rotate)
{
    mode = m_size_mode;
    if(originalSize.width() * m_item_height == m_item_width * originalSize.height())
        mode = ShrinkImage;
    rotate = m_autorotate && ((originalSize.height() > originalSize.width()) != (m_item_height > m_item_width));
}

QList<QImage> Engine::takeRow(QList<QImage> &images, double width, double margin)
{
    QList<QImage> row;
    while(!images.empty()) {
        QImage & img = images.front();
        double iw = toCentimeters(img.width());
        if(width >= iw) {
            width -= iw;
            width -= margin;
            row.append(img);
            images.removeFirst();
        }
        else
            break;
    }
    return row;
}

double Engine::toCentimeters(int pixels)
{
    return pixels / 300.0 * 2.54;
}

int Engine::toPixels(double centimeters)
{
    return centimeters * 300.0 / 2.54;
}

void Engine::printMarkers(QPainter &painter, const QRectF &rect)
{
    double a = 0.1;
    double b = 0.3;
    painter.drawLine(rect.topLeft()     + QPointF(-a,  0), rect.topLeft()     + QPointF(-b,  0));
    painter.drawLine(rect.topLeft()     + QPointF( 0, -a), rect.topLeft()     + QPointF( 0, -b));

    painter.drawLine(rect.topRight()    + QPointF( a,  0), rect.topRight()    + QPointF( b,  0));
    painter.drawLine(rect.topRight()    + QPointF( 0, -a), rect.topRight()    + QPointF( 0, -b));

    painter.drawLine(rect.bottomRight() + QPointF( a,  0), rect.bottomRight() + QPointF( b,  0));
    painter.drawLine(rect.bottomRight() + QPointF( 0,  a), rect.bottomRight() + QPointF( 0,  b));

    painter.drawLine(rect.bottomLeft()  + QPointF(-a,  0), rect.bottomLeft()  + QPointF(-b,  0));
    painter.drawLine(rect.bottomLeft()  + QPointF( 0,  a), rect.bottomLeft()  + QPointF( 0,  b));
}

void Engine::enable(bool enable)
{
    m_mutex.lock();
    m_enabled = enable;
    m_cond.wakeAll();
    m_mutex.unlock();
}

void Engine::setAutoRotate(bool rotate)
{
    m_mutex.lock();
    m_autorotate = rotate;
    m_cond.wakeAll();
    m_mutex.unlock();
}

void Engine::setSizeMode(Engine::SizeMode mode)
{
    m_mutex.lock();
    m_size_mode = mode;
    m_cond.wakeAll();
    m_mutex.unlock();
}

void Engine::setPrintMarkers(bool markers)
{
    m_markers = markers;
    emit changed();
}

void Engine::setItemWidth(double width)
{
    m_mutex.lock();
    m_item_width = width;
    m_cond.wakeAll();
    m_mutex.unlock();
}

void Engine::setItemHeight(double height)
{
    m_mutex.lock();
    m_item_height = height;
    m_cond.wakeAll();
    m_mutex.unlock();
}
